#include "semaphore.h"
#include "conductor.h"
#include "interrupt.h"
#include "assert.h"

/**
 * initialize the specified semaphore
 * @param psem: the descriptor of semaphore
 * @param tcount: total count of semaphore, tcount >= 1
 * @param vcount: valid count of semaphore, the number of semaphores available for initialization, vcount <= tcount
 * @return: void
 */
void mx_semaphore_init (mx_semaphore_pt psem, mx_usize_t tcount, mx_usize_t vcount)
{
    psem->vc = vcount;
    psem->ivc = tcount;
    psem->suspend = mx_null;
}

/**
 * waiting for a semaphore to be sign within the specified time
 * @param psem: the descriptor of semaphore
 * @param tick: timeout, can not be 0
 * @note: this function can't be called in interrupt and it can interrupt nest
 * @return: if any error occured or timeout
 */
mx_bool_t mx_semaphore_wait (mx_semaphore_pt psem, mx_usize_t tick)
{
    assert(tick == 0);

    mx_usize_t bcount;
    mx_bool_t fst = mx_true;    // first in
    /* avoid mx_semaphore_sign function called in interrupt and access vc too,
       and don't want enalbe interrupt before mx_thread_suspend */
    mx_usize_t istate = mx_interrupt_disable_nest();

    mx_thread_pt activetd = mx_conductor_active();
    /* vc == 0 means that there is no valid count for applicant,
       the `vc` may be modify by other thread or interrupt, and the compiler can't trace, so need volatile */
    while ((bcount = mx_thread_bcountc(activetd), mx_true) && psem->vc == 0)
    {
        /* check if thread resumed by mx_semaphore_sign(), != MX_THREAD_BCOUNT_INITIAL */
        if (bcount != MX_THREAD_BCOUNT_INITIAL || ({ mx_bool_t tmp = fst; fst = mx_false; bcount = 0; tmp; }))
        {
            tick -= bcount;
        }
        else
        {
            mx_interrupt_enable_nest(istate);
            return mx_false;
        }
        /* do suspend and intert the thread to sem's suspend-chain,
           needn't enable interrupt before call the mx_thread_suspend function */
        if (psem->suspend == mx_null)
        {
            psem->suspend = &activetd->list;
            mx_thread_suspend(activetd, mx_null, tick);
        }
        else
        {
            mx_thread_suspend(activetd, psem->suspend, tick);
        }
        /* resumed by timerout or sign here */
        /* if is resumed by other thread or interrupt (not timeout) and the vc != 0, the bcount will record `curcnt`
           and we should reset it, so called the mx_thread_bcountc() in while() */
    }
    psem->vc --;
    mx_interrupt_enable_nest(istate);

    return mx_true;
}

/**
 * waiting for a semaphore to be sign forever
 * @param psem: the descriptor of semaphore
 * @note: this function can't be called in interrupt and it can interrupt nest
 * @return: void
 */
void mx_semaphore_waitforever (mx_semaphore_pt psem)
{
    /* avoid mx_semaphore_sign function called in interrupt and access vc too,
       and don't want enalbe interrupt before mx_thread_suspend */
    mx_usize_t istate = mx_interrupt_disable_nest();

    mx_thread_pt activetd = mx_conductor_active();
    /* vc == 0 means that there is no valid count for applicant
       the `vc` may be modify by other thread or interrupt, and the compiler can't trace, so need volatile */
    while (psem->vc == 0)
    {
        /* do suspend and intert the thread to sem's suspend-chain,
           needn't enable interrupt before call the mx_thread_suspend function */
        if (psem->suspend == mx_null)
        {
            psem->suspend = &activetd->list;
            mx_thread_suspend(activetd, mx_null, 0);
        }
        else
        {
            mx_thread_suspend(activetd, psem->suspend, 0);
        }
        /* resumed by timerout or sign here */
    }
    psem->vc --;
    mx_interrupt_enable_nest(istate);
}

/**
 * sign a semaphore
 * @param psem: the descriptor of semaphore
 * @note: this function can be called in interrupt and it can interrupt nest
 * @return: void
 */
void mx_semaphore_sign (mx_semaphore_pt psem)
{
    mx_usize_t istate = mx_interrupt_disable_nest();
    if (psem->vc < psem->ivc)
    {
        psem->vc ++;
        /* the vd must be 0 when psem->suspend == null */
        if (psem->suspend != mx_null)
        {
            mx_dlist_pt tmp = psem->suspend;
            /* all of suspend thread has been set to ready, therefore set it to null */
            psem->suspend = mx_null;
            /* needn't enable interrupt before call the mx_thread_suspend function */
            mx_thread_resumeall(tmp);
        }
    }
    mx_interrupt_enable_nest(istate);
}
